package com.kaia.shoebox.adapter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;

import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.kaia.shoebox.R;
import com.kaia.shoebox.db.dao.TodoItem;

public class ShoeboxListAdapter extends BaseAdapter implements Filterable {

	// ------------------------------------------------------------------
	// Attributes
	// ------------------------------------------------------------------

	private List<TodoItem> mObjects, mDisplayedObjects;

	private Boolean mOutOfSync = false;
	private boolean mNotifyChange = true;

	private TodoItemListFilter mFilter;
	private CharSequence mLastFilter;

	private Context mCtx;

	private Object mFilterLock = new Object();

	// ------------------------------------------------------------------
	// Constructors
	// ------------------------------------------------------------------

	public ShoeboxListAdapter(Context ctx, List<TodoItem> list) {
		mCtx = ctx;
		mObjects = list;
		mDisplayedObjects = new ArrayList<TodoItem>(mObjects);
	}

	public ShoeboxListAdapter(Context ctx) {
		mCtx = ctx;
		mObjects = new ArrayList<TodoItem>();
		mDisplayedObjects = new ArrayList<TodoItem>(mObjects);
	}

	// ------------------------------------------------------------------
	// System Methods
	// ------------------------------------------------------------------

	@Override
	public void notifyDataSetChanged() {
		boolean reapplyFilter;

		synchronized (mFilterLock) {
			reapplyFilter = mOutOfSync = (mLastFilter != null);
		}

		if (reapplyFilter) {
			getFilter().filter(mLastFilter);
		} else {
			synchronized (mObjects) {
				mDisplayedObjects = new ArrayList<TodoItem>(mObjects);
			}
		}

		doNotifyDataSetChanged();
	}

	// ------------------------------------------------------------------
	// Methods
	// ------------------------------------------------------------------

	protected boolean isOutOfSync() {
		return mOutOfSync;
	}

	protected void setOutOfSync(boolean sync) {
		mOutOfSync = sync;
	}

	protected void setNotifyOnChange(boolean notify) {
		mNotifyChange = notify;
	}

	public int getCount() {
		return mDisplayedObjects.size();
	}

	public TodoItem getItem(int pos) {
		return mDisplayedObjects.get(pos);
	}

	public long getItemId(int position) {
		return getItem(position).getId();
	}

	public View getView(int position, View convertView, ViewGroup parent) {

		final ViewHolder holder;

		if (convertView == null) {
			convertView = (LinearLayout) LayoutInflater.from(mCtx).inflate(
					R.layout.main_list_item, parent, false);

			holder = new ViewHolder();
			holder.title = (TextView) convertView
					.findViewById(R.id.item_todo_title);
			holder.dueDate = (TextView) convertView
					.findViewById(R.id.item_due_date_tag);
			convertView.setTag(holder);

		} else {
			holder = (ViewHolder) convertView.getTag();
		}

		TodoItem item = getItem(position);

		long millis = item.getDueDate().getTime() - System.currentTimeMillis();

		holder.dueDate
				.setText(String.format(getReadableDateDifference(millis)));
		holder.title.setText(item.getTitle());

		switch (item.getImportance()) {
		case HIGH:
			holder.dueDate.setTextColor(Color.rgb(180, 0, 0));
			break;
		case MEDIUM_HIGH:
			holder.dueDate.setTextColor(Color.rgb(255, 147, 0));
			break;
		case MEDIUM:
			holder.dueDate.setTextColor(Color.rgb(220, 200, 15));
			break;
		case LOW:
			holder.dueDate.setTextColor(Color.rgb(126, 201, 10));
			break;
		case DONE:
			holder.dueDate.setTextColor(Color.rgb(37, 149, 195));
			break;
		}

		return convertView;
	}

	protected void doNotifyDataSetChanged() {
		super.notifyDataSetChanged();
	}

	// ------------------------------------------------------------------
	// List Handling Methods
	// ------------------------------------------------------------------

	public void addAll(List<TodoItem> list) {
		synchronized (mObjects) {
			if (list != null) {
				mObjects.addAll(list);
			}
		}

		if (mNotifyChange) {
			notifyDataSetChanged();
		}
	}

	public void clear() {
		synchronized (mObjects) {
			mObjects.clear();
		}

		if (mNotifyChange) {
			notifyDataSetChanged();
		}
	}

	public void remove(TodoItem item) {
		synchronized (mObjects) {
			mObjects.remove(item);
		}

		if (mNotifyChange) {
			notifyDataSetChanged();
		}
	}

	public void add(TodoItem item) {
		synchronized (mObjects) {
			mObjects.add(item);
		}

		if (mNotifyChange) {
			notifyDataSetChanged();
		}
	}

	public void update(TodoItem newItem, int itemId) {
		synchronized (mObjects) {
			for (TodoItem item : mObjects) {
				if (item.getId() == itemId) {
					item = newItem;
					return;
				} else {
					throw new RuntimeException("WTF!?!, the ID doen't exist...");
				}
			}
		}

		if (mNotifyChange) {
			notifyDataSetChanged();
		}
	}

	public void sortByImportance() {
		synchronized (mObjects) {
			Collections.sort(mObjects, new Comparator<TodoItem>() {

				public int compare(TodoItem lhs, TodoItem rhs) {
					if (lhs.getImportance().compare(rhs.getImportance()) == 0) {
						return lhs.getDueDate().compareTo(rhs.getDueDate());
					} else
						return lhs.getImportance().compare(rhs.getImportance());
				}
			});
		}

		if (mNotifyChange) {
			notifyDataSetChanged();
		}
	}

	public void sortByDueDate() {
		synchronized (mObjects) {
			Collections.sort(mObjects, new Comparator<TodoItem>() {

				public int compare(TodoItem lhs, TodoItem rhs) {
					return lhs.getDueDate().compareTo(rhs.getDueDate());
				}
			});
		}

		if (mNotifyChange) {
			notifyDataSetChanged();
		}
	}

	public void sortByCreatedDate() {
		synchronized (mObjects) {
			Collections.sort(mObjects, new Comparator<TodoItem>() {

				public int compare(TodoItem lhs, TodoItem rhs) {
					return lhs.getDateCreated().compareTo(rhs.getDateCreated());
				}
			});
		}

		if (mNotifyChange) {
			notifyDataSetChanged();
		}
	}

	// ------------------------------------------------------------------
	// Utility Methods
	// ------------------------------------------------------------------

	private String getReadableDateDifference(long millis) {

		String readableDate = "";

		int minutes = (int) ((millis / (1000 * 60)) % 60);
		int hours = (int) ((millis / (1000 * 60 * 60)) % 24);
		int days = (int) (millis / (1000 * 60 * 60 * 24));
		int months = (int) ((float) (days / 30.4368499f));
		int years = (int) ((float) (days) / 365.242199f);

		if (millis > 0) {
			if (years >= 1) {
				readableDate = String.format("Due in %d years", years);
			} else if (months > 1 && months < 12) {
				readableDate = String.format("Due in %d months", months);
			} else if (days > 15 && (days < 30 || days < 31)) {
				readableDate = String.format("Due in %d days ", days);
			} else if (days >= 1 && days <= 15) {
				readableDate = String.format("Due in %d days and %d hours",
						days, hours);
			} else if (hours >= 1 && hours < 24) {
				readableDate = String.format("Due in %d hours and %d minutes",
						hours, minutes);
			} else {
				readableDate = String.format("Due in %d minutes", minutes);
			}
		} else {
			if (Math.abs(years) >= 1) {
				readableDate = String.format("Overdue by more than %d years",
						Math.abs(years));
			} else if (Math.abs(months) > 1 && Math.abs(months) < 12) {
				readableDate = String.format("Overdue by %d months",
						Math.abs(months));
			} else if (Math.abs(days) > 15
					&& (Math.abs(days) < 30 || Math.abs(days) < 31)) {
				readableDate = String.format("Overdue by %d days",
						Math.abs(days));
			} else if (Math.abs(days) >= 1 && Math.abs(days) <= 15) {
				readableDate = String.format("Overdue by %d days and %d hours",
						Math.abs(days), Math.abs(hours));
			} else if (Math.abs(hours) >= 1 && Math.abs(hours) < 24) {
				readableDate = String.format(
						"Overdue by %d hours and %d minutes", Math.abs(hours),
						Math.abs(minutes));
			} else {
				readableDate = String.format("Overdue by %d minutes",
						Math.abs(minutes));
			}
		}
		return readableDate;
	}

	// ------------------------------------------------------------------
	// ViewHolder
	// ------------------------------------------------------------------

	public static class ViewHolder {
		private TextView title;
		private TextView dueDate;

		public TextView getTitle() {
			return title;
		}

		public TextView getDueDate() {
			return dueDate;
		}

	}

	// ------------------------------------------------------------------
	// Filtering Stuff
	// ------------------------------------------------------------------

	public Filter getFilter() {
		if (mFilter == null) {
			mFilter = new TodoItemListFilter();
		}

		return mFilter;
	}

	private boolean passesFilter(TodoItem object, String constraint) {
		String repr = object.getTitle().toLowerCase();

		if (repr.startsWith(constraint))
			return true;

		else {
			final String[] words = repr.split(" ");
			final int wordCount = words.length;

			for (int i = 0; i < wordCount; i++) {
				if (words[i].startsWith(constraint))
					return true;
			}
		}
		return false;
	}

	private class TodoItemListFilter extends Filter {

		@Override
		protected FilterResults performFiltering(CharSequence prefix) {

			ArrayList<TodoItem> filteredObjects;

			synchronized (mFilterLock) {
				if (!mOutOfSync && mLastFilter != null
						&& mLastFilter.equals(prefix)) {
					return null;
				}

				mOutOfSync = false;
				mLastFilter = prefix;
			}

			synchronized (mObjects) {
				filteredObjects = new ArrayList<TodoItem>(mObjects);
			}

			if (prefix == null || prefix.length() == 0) {
				return resultsFromList(filteredObjects);
			}

			String constraint = prefix.toString().toLowerCase();

			ListIterator<TodoItem> it = filteredObjects.listIterator();

			while (it.hasNext()) {
				TodoItem item = it.next();

				if (!passesFilter(item, constraint)) {
					it.remove();
				}
			}
			return resultsFromList(filteredObjects);
		}

		@SuppressWarnings("unchecked")
		@Override
		protected void publishResults(CharSequence constraint,
				FilterResults results) {
			if (results != null) {
				mDisplayedObjects = (List<TodoItem>) results.values;
			}
			doNotifyDataSetChanged();
		}

		protected FilterResults resultsFromList(List<TodoItem> list) {
			FilterResults fr = new FilterResults();

			fr.values = list;
			fr.count = list.size();

			return fr;
		}
	}
}
